package vm

import (
	"reflect"
	"sync"

	"github.com/spf13/cast"
	libs "github.com/vadv/gopher-lua-libs"
	lua "github.com/yuin/gopher-lua"
)

// 默认虚拟机池的大小
const defaultPoolSize = 10

type Luavm struct {
	VM *lua.LState
}

// Lua虚拟机池
type LStatePool struct {
	Lvm      []*Luavm
	PutCount int // 栈中虚拟机个数
	GetCount int // 出栈了多少个虚拟机
	Lock     sync.Mutex
}

// 加载基础第三方包
func (lv *Luavm) loadPkg() {
	libs.Preload(lv.VM)
}

// LSetGStr
// 给虚拟机传入全局字符变量
func (lv *Luavm) SetGStr(name, data string) {
	lv.VM.SetGlobal(name, lua.LString(data))
}

// LGetGStr
// 获取lua中的全局变量的值
func (lv *Luavm) GetGStr(name string) string {
	return lv.VM.GetGlobal(name).String()
}

// 将map[string]interface{} 转为lua table
func (lv *Luavm) SetGTableForMap(name string, datas map[string]interface{}) {
	var t = lv.VM.NewTable()
	for k, v := range datas {
		switch data := v.(type) {
		case string:
			t.RawSetString(k, lua.LString(data))
		case int, int32, int64, uint, uint32, uint64:
			t.RawSetString(k, lua.LNumber(cast.ToFloat64(data)))
		case float32, float64:
			t.RawSetString(k, lua.LNumber(cast.ToFloat64(data)))
		case bool:
			t.RawSetString(k, lua.LBool(data))
		case map[string]interface{}:
			lv.SetGTableForMap(k, data)
		}
	}
	lv.VM.SetGlobal(name, t)
}

// 将结构体 转为lua table 暂时只支持一层结构体
func (lv *Luavm) SetGTableForStruct(name string, datas interface{}) {
	var t = lv.VM.NewTable()
	vRef := reflect.ValueOf(datas)
	tRef := reflect.TypeOf(datas)
	// 只处理结构体
	if vRef.Kind() == reflect.Ptr {
		dRef := vRef.Elem()
		if dRef.Kind() == reflect.Struct {
			// 循环处理
			for i := 0; i < dRef.NumField(); i++ {
				name := tRef.Elem().Field(i).Name
				val := cast.ToString(dRef.Field(i).Interface())
				t.RawSetString(name, lua.LString(val))
			}
		}
	}
	lv.VM.SetGlobal(name, t)
}

// 将lua table 转为 结构体
func (lv *Luavm) GetGTable2Struct(name string, datas interface{}) {
	v := lv.VM
	d := v.GetGlobal(name)
	telem := reflect.TypeOf(datas).Elem()

	vRef := reflect.ValueOf(datas)
	dRef := vRef.Elem()
	if telem.Kind() == reflect.Struct {
		for i := 0; i < telem.NumField(); i++ {
			name := telem.Field(i).Name
			luaData := v.GetTable(d, lua.LString(name))
			if dRef.Field(i).CanSet() {
				switch dRef.Field(i).Kind() {
				case reflect.String:
					dRef.Field(i).SetString(luaData.String())
				case reflect.Bool:
					dRef.Field(i).SetBool(cast.ToBool(luaData.String()))
				case reflect.Float32, reflect.Float64, reflect.Int32, reflect.Int64:
					dRef.Field(i).SetInt(cast.ToInt64(luaData.String()))
				}
			}
		}
	}
}

// RunLua
// 运行lua脚本，可以在运行的时候传入事件，
// 在运行lua脚本之前，给lua传入方法和变量、模块等
func (lv *Luavm) RunLua() error {
	v := lv.VM
	lfunc := v.NewFunctionFromProto(Proto)
	v.Push(lfunc)
	err := v.PCall(0, lua.MultRet, nil)
	return err
}

// NewLuaVMPool
// 使用此方法新建一个虚拟机池
func NewLuaVMPool(size ...int) *LStatePool {
	poolSize := defaultPoolSize
	if len(size) > 0 {
		poolSize = size[0]
	}

	p := new(LStatePool)
	p.Lvm = make([]*Luavm, 0)
	for i := 0; i < poolSize; i++ {
		// 新建一个虚拟机
		vm := p.New()
		p.Lvm = append(p.Lvm, vm)
	}
	return p
}

// 出栈一个Lua 虚拟机
func (p *LStatePool) Get() *Luavm {
	p.Lock.Lock()
	defer p.Lock.Unlock() // defer 先入后出

	n := len(p.Lvm)
	if n == 0 {
		return p.New()
	}
	x := p.Lvm[n-1]
	p.Lvm = p.Lvm[0 : n-1]
	return x
}

// 入栈一个lua 虚拟机
func (p *LStatePool) Put(L *Luavm) {
	p.Lock.Lock()
	defer p.Lock.Unlock() // defer 先入后出
	p.Lvm = append(p.Lvm, L)
}

// 新建一个lua 虚拟机
func (pl *LStatePool) New() *Luavm {
	vm := new(Luavm)
	// 加载基础模块
	//vm.VM = lua.NewState(lua.Options{SkipOpenLibs: true})
	vm.VM = lua.NewState()
	// 加载模块
	vm.loadPkg()
	return vm
}

// 关闭lua虚拟机池
func (pl *LStatePool) Shutdown() {
	for _, L := range pl.Lvm {
		L.VM.Close()
	}
}
